/*
1.简单日志宏实现
意义：快速定位程序运⾏逻辑出错的位置。
2.Json序列化/反序列化
3.UUID⽣成
UUID(Universally Unique Identifier), 也叫通⽤唯⼀识别码，通常由32位16进制数字字符组成。
UUID的标准型式包含32个16进制数字字符，以连字号分为五段，形式为8-4-4-4-12的32个字符，
如：550e8400-e29b-41d4-a716-446655440000。
在这⾥，uuid⽣成，我们采用⽣成8个随机数字，加上8字节序号，共16字节数组生成32位16进制字符
的组合形式来确保全局唯⼀的同时能够根据序号来分辨数据。
*/
#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include <stdarg.h>
#include <pthread.h>
#include <fstream>
#include <jsoncpp/json/json.h>
#include <sstream>
#include <memory>
#include <chrono>
#include <random>
#include <atomic>
#include <iomanip>

namespace rpc
{
    class LockGuard
    {
    public:
        LockGuard(pthread_mutex_t *mutex) : _mutex(mutex)
        {
            // 加锁
            pthread_mutex_lock(_mutex);
        }
        ~LockGuard()
        {
            // 解锁
            pthread_mutex_unlock(_mutex);
        }

    private:
        // pthread_mutex_t* _mutex;
        pthread_mutex_t *_mutex;
    };

    // 日志等级
    enum Level
    {
        DEBUG = 0,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

#define LDEFAULT DEBUG
    bool gIsSave = false;
    const std::string logname = "log.txt";
    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    std::string LevelToString(int level)
    {
        switch (level)
        {
        case DEBUG:
            return "Debug";
        case INFO:
            return "Info";
        case WARNING:
            return "Warning";
        case ERROR:
            return "Error";
        case FATAL:
            return "Fatal";
        default:
            return "Unknown";
        }
    }

    std::string GetTimeString()
    {
        time_t curr_time = time(nullptr);
        struct tm *formate_time = localtime(&curr_time);
        if (formate_time == nullptr)
            return "None";
        char time_buffer[1024];
        snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",
                 formate_time->tm_year + 1900, formate_time->tm_mon + 1, formate_time->tm_mday,
                 formate_time->tm_hour, formate_time->tm_min, formate_time->tm_sec);
        return time_buffer;
    }

    void SaveFile(const std::string &filename, std::string &message)
    {
        std::ofstream out(filename, std::ios::app);//Seek to end before each write
        if (!out.is_open())
        {
            return;
        }
        out << message;
        out.close();
    }

    // 日志格式
    // 日志等级 时间  代码所在文件名/行号 日志内容
    void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...)
    {
        if (level < LDEFAULT)
            return;
        std::string levelstr = LevelToString(level);
        std::string timestr = GetTimeString();
        pid_t selfid = getpid();
        char buffer[1024];
        va_list arg;
        va_start(arg, format);
        vsnprintf(buffer, sizeof(buffer), format, arg);
        va_end(arg);
        LockGuard log(&lock);
        std::string message = "[" + timestr + "]" + "[" + levelstr + "]" + "[pid:" + std::to_string(selfid) + "]" + "[" + filename + "]" + "[" + std::to_string(line) + "] " + buffer + "\n";
        if (!issave)
            std::cout << message << std::endl;
        else
            SaveFile(logname, message);
    }

#define EnableFile()    \
    do                  \
    {                   \
        gIsSave = true; \
    } while (0)
#define EnableScreen()   \
    do                   \
    {                    \
        gIsSave = false; \
    } while (0)
//宏的作用域：宏在预处理阶段展开时，不会考虑命名空间。即使在 rpc 命名空间内定义了宏 , 宏 在命名空间外 被调用时也会直接展开。
//命名空间问题：LogMessage是在 rpc 命名空间内定义的，因此，如果在 rpc 命名空间外部直接调用 LOG()（宏展开为 LogMessage())，编译器就无法找到 LogMessage()，因为它没有在当前作用域中。
#define LOG(level, formate, ...)                                                \
    do                                                                          \
    {                                                                           \
        rpc::LogMessage(__FILE__, __LINE__, rpc::gIsSave, rpc::level, formate, ##__VA_ARGS__); \
    } while (0)

    class JSON
    {
    public:
        // 序列化
        static bool serialize(const Json::Value &val, std::string &body)
        {
            std::stringstream ss;
            Json::StreamWriterBuilder swb;
            std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
            int n = sw->write(val, &ss);
            if (n != 0)
            {
                LOG(ERROR, "Json serialize failed");
                return false;
            }
            body = ss.str();
            return true;
        }

        // 反序列化
        static bool unserialize(const std::string &body, Json::Value &val)
        {
            Json::CharReaderBuilder crb;
            std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
            std::string err;
            bool ret = cr->parse(body.c_str(), body.c_str() + body.size(), &val, &err);
            if (!ret)
            {
                LOG(ERROR, "Json unserialize failed: %s", err.c_str());
                return false;
            }
            return true;
        }
    };

    class UUID
    {
    public:
        static std::string uuid()
        {
            std::stringstream ss;//字符串流
            //1. 构造⼀个机器随机数对象
            std::random_device rd;
            //2. 以机器随机数为种⼦构造伪随机数对象
            std::mt19937 generator(rd());
            //3. 构造限定数据范围的对象
            std::uniform_int_distribution<int> distribute(0,255);
            //4. ⽣成8个随机数，按照特定格式组织成为16进制数字字符的字符串
            for(int i = 0;i<8;i++)
            {
                if(i ==4||i == 6) ss<<"-";
                ss<<std::setw(2)<<std::setfill('0')<<std::hex<<distribute(generator);//setw(2)两个字符宽度,setfill('0')不够2字符,前面补零  (hex)十进制转换为16进制
            }
            ss<<"-";//继续追加一个分隔符
            //5. 定义⼀个8字节序号，逐字节组织成为16进制数字字符的字符串
            static std::atomic<ssize_t> seq(1);
            size_t cur = seq.fetch_add(1);//每次进行+1
            for(int i = 7;i>=0;i--)//00 00 00 00 00 00 00 01 ->从左向右进行提取
            {
                if(i ==5) ss<<"-";
                ss<<std::setw(2)<<std::setfill('0')<<std::hex<<((cur>>(i*8))&0xFF);//取出一个字节
            }
            return ss.str();
        }
    };
}